/*------------------------------------------------------------------------------
 *
 * CFiniteStateTransducer
 *
 *----------------------------------------------------------------------------*/

#include "CFiniteStateTransducer.hpp"

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <string.h>

#include "CMatrix.hpp"
#include "CMatrixIO.hpp"
#include "CFileTxtIn.hpp"
#include "CFileNameHelper.hpp"
#include "asrtbas.h"
using namespace std;


struct prv_finiteStateTransducer_t
{
    CMatrix *S, *F;

    vector< CMatrix* > listM;

    unsigned int numInputSymbols, numOutputSymbols;
};

//------------------------------------------------------------------------------
//
static struct prv_finiteStateTransducer_t* prv_create( unsigned int numInputSymbols,
                        unsigned int numOutputSymbols )
{
    prv_finiteStateTransducer_t *data = new prv_finiteStateTransducer_t;
    assert_no_null( data );

    data->numInputSymbols = numInputSymbols;
    data->numOutputSymbols = numOutputSymbols;

    data->S = NULL;
    data->F = NULL;

    return data;
}

//------------------------------------------------------------------------------
//
CFiniteStateTransducer ::
CFiniteStateTransducer( unsigned int numInputSymbols, unsigned int numOutputSymbols,
                        unsigned int num_states )
{
    assert( num_states > 0 );
    assert( numInputSymbols > 0 );

    m_data = prv_create( numInputSymbols, numOutputSymbols );

    m_data->S = new CMatrix( 1, num_states, 0.0 );
    m_data->F = new CMatrix( num_states, 1, 0.0 );

    // Generate one matrix for each pair of symbols
    unsigned long totalSymbols = this->totalSymbols();

    for( unsigned long n = 0; n < totalSymbols; ++n )
    {
        CMatrix *M = new CMatrix( num_states, num_states, 0.0 );

        m_data->listM.push_back( M );
    }
}

//------------------------------------------------------------------------------
//
static void prv_removeFileCommentLines( CFileTxtIn **file )
{
    char c;
    bool end = false;

    do
    {
        if( c == '#' )
            (*file)->getString();

        c = (*file)->getChar();

        if( c != '#' && c != ' ' && c != '\n' && c != '\t' )
            end = true;

    } while( end == false );

    (*file)->ungetChar();
}

//------------------------------------------------------------------------------
//
CFiniteStateTransducer :: CFiniteStateTransducer( const char* filename )
{
    assert( strcmp( CFileNameHelper::getExtension( filename ), ".fst" ) == 0 );

    CFileTxtIn *file = new CFileTxtIn( filename );
    assert_no_null( file );

    prv_removeFileCommentLines( &file );

    unsigned int totalInputSymbols = file->getUInt();
    unsigned int totalOutputSymbols = file->getUInt();
    unsigned int num_states = file->getUInt();

    assert( totalInputSymbols > 0 );
    assert( totalOutputSymbols > 0 );
    assert( num_states > 0 );


    m_data = prv_create( totalInputSymbols, totalOutputSymbols );


    prv_removeFileCommentLines( &file );

    m_data->S = CMatrixIO::streamLoad( &file );

    prv_removeFileCommentLines( &file );

    m_data->F = CMatrixIO::streamLoad( &file );


    for( unsigned int input_symbol = 0; input_symbol < totalInputSymbols; ++input_symbol )
    {
        for( unsigned int output_symbol = 0; output_symbol < totalOutputSymbols; ++output_symbol )
        {
            prv_removeFileCommentLines( &file );

            unsigned int current_input_symbol = file->getUInt();
            unsigned int current_output_symbol = file->getUInt();

            assert( current_input_symbol == input_symbol );
            assert( current_output_symbol == output_symbol );

            CMatrix *matrix = CMatrixIO::streamLoad( &file );

            m_data->listM.push_back( matrix );
        }
    }

}

//------------------------------------------------------------------------------
//
CFiniteStateTransducer ::
CFiniteStateTransducer( const CFiniteStateTransducer *transducer )
{
    assert_no_null( transducer );

    m_data = prv_create( transducer->m_data->numInputSymbols,
                         transducer->m_data->numOutputSymbols );

    assert_no_null( transducer->m_data->S );
    assert_no_null( transducer->m_data->F );
    m_data->S = new CMatrix( transducer->m_data->S );
    m_data->F = new CMatrix( transducer->m_data->F );

    for( vector< CMatrix* >::const_iterator it = transducer->m_data->listM.begin();
         it != transducer->m_data->listM.end(); ++it )
    {
        assert_no_null( *it );
        CMatrix *M = new CMatrix( *it );

        m_data->listM.push_back( M );
    }
}

//------------------------------------------------------------------------------
//
CFiniteStateTransducer :: ~CFiniteStateTransducer()
{
    assert_no_null( m_data->S );
    assert_no_null( m_data->F );

    delete m_data->S;
    delete m_data->F;

    m_data->listM.clear();

    delete m_data;
}

//------------------------------------------------------------------------------
//
static string prv_intToString( int value )
{
    ostringstream out;
    out << value;
    return out.str();
}

//------------------------------------------------------------------------------
//
static long prv_getMatrixID( unsigned int input_symbol, unsigned int output_symbol,
                             unsigned int totalInputSymbols, unsigned int totalOutputSymbols )
{
    assert( input_symbol == 0 || input_symbol < totalInputSymbols );
    assert( output_symbol == 0 || output_symbol < totalOutputSymbols );

    if( totalOutputSymbols == 0 && totalInputSymbols == 0 )
        return -1;

    else if( totalOutputSymbols == 0 )
        return input_symbol;

    else if( totalInputSymbols == 0 )
        return output_symbol;

    else
        return (input_symbol * totalOutputSymbols) + output_symbol;
}

//------------------------------------------------------------------------------
//
void CFiniteStateTransducer :: print() const
{
    CMatrixIO::print( m_data->S, "S__size" );
    CMatrixIO::print( m_data->F, "F__size" );

    unsigned int totalInputSymbols = m_data->numInputSymbols;
    unsigned int totalOutputSymbols = m_data->numOutputSymbols;
    unsigned long id;
    string label;

    assert( totalInputSymbols > 0 );

    if( totalOutputSymbols == 0 )
    {
        unsigned int numSymbol = 0;

        for( vector< CMatrix* >::const_iterator it = m_data->listM.begin();
             it != m_data->listM.end(); ++it, ++numSymbol )
        {
            label = "M{"
                    + prv_intToString( numSymbol )
                    +"}__size";

            CMatrixIO::print( *it, label.c_str() );
        }
    }
    else
    {
        for( unsigned int input_symbol = 0; input_symbol < totalInputSymbols; ++input_symbol )
        {
            for( unsigned int output_symbol = 0; output_symbol < totalOutputSymbols; ++output_symbol )
            {
                label = "M{"
                        + prv_intToString( input_symbol )
                        + ","
                        + prv_intToString( output_symbol )
                        +"}__size";

                id = prv_getMatrixID( input_symbol, output_symbol,
                                      totalInputSymbols, totalOutputSymbols );

                assert( id >= 0 );

                CMatrixIO::print( m_data->listM[ id ], label.c_str() );
            }
        }
    }
}

//------------------------------------------------------------------------------
//
void CFiniteStateTransducer :: normalizeUsingJointProbability()
{
    assert_no_null( m_data->F );
    assert( m_data->F->size() > 0 );

    unsigned num_states = m_data->F->rows();
    double totalRow;

    for( unsigned r = 0; r < num_states; ++r )
    {
        totalRow = m_data->F->getValue(r,0);

        for( vector< CMatrix* >::const_iterator it = m_data->listM.begin();
             it != m_data->listM.end(); ++it )
        {
            totalRow += (*it)->sumRow( r );
        }

        // NORMALIZE

        m_data->F->setValue( r, 0, m_data->F->getValue(r,0) / totalRow );

        for( vector< CMatrix* >::iterator it = m_data->listM.begin();
             it != m_data->listM.end(); ++it )
        {
            for( unsigned c = 0; c < num_states; ++c )
            {
                (*it)->setValue( r, c, (*it)->getValue(r,c) / totalRow );
            }
        }
    }
}

//------------------------------------------------------------------------------
//
void CFiniteStateTransducer :: generateRandomProbability()
{
    m_data->S->setAll( 0 );
    m_data->S->setValue( 0, 0, 1 );

    m_data->F->generateRandomData( 0 , 100 );

    for( vector< CMatrix* >::iterator it = m_data->listM.begin();
         it != m_data->listM.end(); ++it )
    {
        (*it)->generateRandomData( 0, 100 );
    }

    this->normalizeUsingJointProbability();
}

//------------------------------------------------------------------------------
//
static CMatrix* prv_getTotalMatrixOfInputSymbol( unsigned int input_symbol,
                    unsigned int num_states, const vector< CMatrix* > &listMatrix,
                    unsigned int totalInputSymbols, unsigned int totalOutputSymbols )
{
    assert( num_states > 0 );
    assert( input_symbol == 0 || input_symbol < totalInputSymbols );

    CMatrix *totalMatrixOfSymbols = new CMatrix( num_states, num_states, 0.0 );
    CMatrix *ptrAux;
    unsigned long id;

    for( unsigned int output_symbol = 0; output_symbol < totalOutputSymbols; ++output_symbol )
    {
        id = prv_getMatrixID( input_symbol, output_symbol,
                              totalInputSymbols, totalOutputSymbols );

        ptrAux = totalMatrixOfSymbols;
        totalMatrixOfSymbols = ptrAux->matrixAddition( listMatrix[ id ] );
        delete ptrAux;
    }

    return totalMatrixOfSymbols;
}

//------------------------------------------------------------------------------
//
void CFiniteStateTransducer :: generateFromInputSymbols( const CFiniteStateTransducer *transducer )
{
    assert( m_data->numInputSymbols == transducer->m_data->numInputSymbols );
    assert( m_data->numOutputSymbols == 0 );

    m_data->S->copyValues( transducer->m_data->S );
    m_data->F->copyValues( transducer->m_data->F );

    unsigned int num_states = m_data->F->rows();

    for( unsigned int n = 0; n < m_data->numInputSymbols; ++n )
    {
        CMatrix *M = prv_getTotalMatrixOfInputSymbol( n, num_states,
                                        transducer->m_data->listM,
                                        transducer->m_data->numInputSymbols,
                                        transducer->m_data->numOutputSymbols );

        m_data->listM[ n ]->copyValues( M );

        delete M;
    }

    this->normalizeUsingJointProbability();
}

//------------------------------------------------------------------------------
//
static CMatrix* prv_getTotalMatrixOfOutputSymbol( unsigned int output_symbol,
                    unsigned int num_states, const vector< CMatrix* > &listMatrix,
                    unsigned int totalInputSymbols, unsigned int totalOutputSymbols )
{
    assert( num_states > 0 );
    assert( output_symbol == 0 || output_symbol < totalOutputSymbols );

    CMatrix *totalMatrixOfSymbols = new CMatrix( num_states, num_states, 0.0 );
    CMatrix *ptrAux;
    unsigned long id;

    for( unsigned int input_symbol = 0; input_symbol < totalInputSymbols; ++input_symbol )
    {
        id = prv_getMatrixID( input_symbol, output_symbol,
                              totalInputSymbols, totalOutputSymbols );

        ptrAux = totalMatrixOfSymbols;
        totalMatrixOfSymbols = ptrAux->matrixAddition( listMatrix[ id ] );
        delete ptrAux;
    }

    return totalMatrixOfSymbols;
}

//------------------------------------------------------------------------------
//
void CFiniteStateTransducer :: generateFromOutputSymbols( const CFiniteStateTransducer *transducer )
{
    assert( m_data->numInputSymbols == transducer->m_data->numOutputSymbols );
    assert( m_data->numOutputSymbols == 0 );

    m_data->S->copyValues( transducer->m_data->S );
    m_data->F->copyValues( transducer->m_data->F );

    unsigned int num_states = m_data->F->rows();

    for( unsigned int n = 0; n < m_data->numInputSymbols; ++n )
    {
        CMatrix *M = prv_getTotalMatrixOfOutputSymbol( n, num_states,
                                        transducer->m_data->listM,
                                        transducer->m_data->numInputSymbols,
                                        transducer->m_data->numOutputSymbols );

        m_data->listM[ n ]->copyValues( M );

        delete M;
    }

    this->normalizeUsingJointProbability();
}

//------------------------------------------------------------------------------
//
void CFiniteStateTransducer :: normalizeUsingInputValues()
{
    assert( m_data->F->size() > 0 );

    unsigned int num_states = m_data->F->rows();

    m_data->F->setAll( 1.0 );

    CMatrix *M;
    double totalRow;
    unsigned long id;

    for( unsigned int r = 0; r < num_states; ++r )
    {
        for( unsigned int input_symbol = 0; input_symbol < m_data->numInputSymbols; ++input_symbol )
        {
            totalRow = 0.0;

            for( unsigned int output_symbol = 0; output_symbol < m_data->numOutputSymbols; ++output_symbol )
            {
                id = prv_getMatrixID( input_symbol, output_symbol,
                                      m_data->numInputSymbols, m_data->numOutputSymbols );

                totalRow += m_data->listM[ id ]->sumRow( r );
            }

            // NORMALIZE

            for( unsigned int output_symbol = 0; output_symbol < m_data->numOutputSymbols; ++output_symbol )
            {
                id = prv_getMatrixID( input_symbol, output_symbol,
                                      m_data->numInputSymbols, m_data->numOutputSymbols );

                M = m_data->listM[ id ];

                for( unsigned int c = 0; c < num_states; ++c )
                {
                    M->setValue( r, c, M->getValue(r,c) / totalRow );
                }
            }
        }
    }
}

//------------------------------------------------------------------------------
//
void CFiniteStateTransducer :: normalizeUsingOutputValues()
{
    assert( m_data->F->size() > 0 );

    unsigned int num_states = m_data->F->rows();

    m_data->F->setAll( 1.0 );

    CMatrix *M;
    double totalRow;
    unsigned long id;

    for( unsigned int r = 0; r < num_states; ++r )
    {
        for( unsigned output_symbol = 0; output_symbol < m_data->numOutputSymbols; ++output_symbol )
        {
            totalRow = 0.0;

            for( unsigned int input_symbol = 0; input_symbol < m_data->numInputSymbols; ++input_symbol )
            {
                id = prv_getMatrixID( input_symbol, output_symbol,
                                      m_data->numInputSymbols, m_data->numOutputSymbols );

                totalRow += m_data->listM[ id ]->sumRow( r );
            }

            // NORMALIZE

            for( unsigned int input_symbol = 0; input_symbol < m_data->numInputSymbols; ++input_symbol )
            {
                id = prv_getMatrixID( input_symbol, output_symbol,
                                      m_data->numInputSymbols, m_data->numOutputSymbols );

                M = m_data->listM[ id ];

                for( unsigned int c = 0; c < num_states; ++c )
                {
                    M->setValue( r, c, M->getValue(r,c) / totalRow );
                }
            }
        }
    }
}

//------------------------------------------------------------------------------
//
unsigned int CFiniteStateTransducer :: numInputSymbols() const
{
    return m_data->numInputSymbols;
}

//------------------------------------------------------------------------------
//
unsigned int CFiniteStateTransducer :: numOutputSymbols() const
{
    return m_data->numOutputSymbols;
}

//------------------------------------------------------------------------------
//
unsigned long CFiniteStateTransducer :: totalSymbols() const
{
    if( m_data->numOutputSymbols == 0 )
    {
        return m_data->numInputSymbols;
    }

    if( m_data->numInputSymbols == 0 )
    {
        return m_data->numOutputSymbols;
    }

    return m_data->numInputSymbols * m_data->numOutputSymbols;
}

//------------------------------------------------------------------------------
//
unsigned int CFiniteStateTransducer :: numStates() const
{
    return m_data->F->rows();
}

//------------------------------------------------------------------------------
//
double CFiniteStateTransducer :: getProbability( const vector<unsigned int> &inputSymbolsList,
                                                 const vector<unsigned int> &outputSymbolsList ) const
{
    assert( m_data->numInputSymbols > 0 );

    CMatrix *M, *Maux;
    unsigned long id;

    M = new CMatrix( m_data->S );


    if( m_data->numOutputSymbols == 0 )
    {
        assert( outputSymbolsList.size() == 0 );

        for( vector<unsigned int>::const_iterator it = inputSymbolsList.begin();
             it != inputSymbolsList.end(); ++it )
        {
            assert( *it < m_data->numInputSymbols );

            id = prv_getMatrixID( *it, 0,
                              m_data->numInputSymbols, m_data->numOutputSymbols );

            Maux = M;

            M = Maux->matrixMultiplication( m_data->listM[ id ] );

            delete Maux;
        }
    }
    else
    {
        assert( inputSymbolsList.size() == outputSymbolsList.size() );

        vector<unsigned int>::const_iterator itIn = inputSymbolsList.begin();
        vector<unsigned int>::const_iterator itOut = outputSymbolsList.begin();

        for( ; itIn != inputSymbolsList.end(); ++itIn, ++itOut )
        {
            assert( *itIn < m_data->numInputSymbols );
            assert( *itOut < m_data->numOutputSymbols );

            id = prv_getMatrixID( *itIn, *itOut,
                              m_data->numInputSymbols, m_data->numOutputSymbols );

            Maux = M;

            M = Maux->matrixMultiplication( m_data->listM[ id ] );

            delete Maux;
        }
    }

    Maux = M;

    M = Maux->matrixMultiplication( m_data->F );

    delete Maux;

    assert( M->size() == 1 );


    return M->getValue( 0, 0 );
}

/**
*/
